//
// Created by 王乐 on 2022/2/11.
//

#include "LeCameraCADBehaviour.h"

#include "glm/gtc/matrix_transform.hpp"

#include "LeInput.h"
#include "LeTime.h"
#include "LeScreenSettings.h"

#include "LeTransform.h"

LeCameraCADBehaviour::LeCameraCADBehaviour()
{}

LeCameraCADBehaviour::~LeCameraCADBehaviour()
{

}

glm::mat4 LeCameraCADBehaviour::getViewMatrix()
{
    return glm::lookAt(m_pTransform->m_Position,
                       m_pTransform->m_Position + Front,
                       Up);
}

glm::mat4 LeCameraCADBehaviour::getProjectionMatrix()
{
    float zratio = 0.02f;
    if(bIsPerspectiveView)
        return glm::perspective(glm::radians(zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
    else
        return glm::ortho(-4.0f*zoom*zratio, 4.0f*zoom*zratio,
                          -3.0f*zoom*zratio, 3.0f*zoom*zratio,
                          0.1f*zoom* zratio, 15.0f*zoom*zratio);
}

void LeCameraCADBehaviour::Start()
{
    m_State = CMS_NONE;

    // fly
    yaw = -90.f;
    pitch = 0.0f;
    moveSpeed = 2.5f;
    mouseSensitivity = 0.1f;
    flatMoveSensitivity = 0.005f;

    // focus

    // common
    zoom = 45.0f;
    bIsPerspectiveView = true;
    Front = glm::vec3(0.0f, 0.0f, -1.0f);
    WorldUp = glm::vec3(0.0f, 1.0f, 0.0f);
    dLastX= SCR_WIDTH / 2.0f;
    dLastY= SCR_HEIGHT / 2.0f;
    focusDistance = 5;

    updateCameraVectors();
    updateCameraFocus();

    isChanging = false;
}

void LeCameraCADBehaviour::Update()
{
    switch (m_State)
    {
        case CMS_NONE:
            processNoneState();
            break;
        case CMS_FLY:
            processFlyState();
            break;
        case CMS_FOCUS:
            processFocusState();
            break;
        case CMS_FLATMOVE:
            processFlatMoveState();
        case CMS_ZOOM:
            processZoomState();
    }
}

void LeCameraCADBehaviour::processNoneState()
{
//    if(LeInput::getMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
    if(LeInput::getKey(GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS)
    {
        m_State = CMS_FLY;
        dLastX = LeInput::getMouseX();
        dLastY = LeInput::getMouseY();
    }
    else if(LeInput::getKey(GLFW_KEY_LEFT_ALT) == GLFW_PRESS)
    {
        m_State = CMS_FOCUS;
        dLastX = LeInput::getMouseX();
        dLastY = LeInput::getMouseY();
    }
    else if(LeInput::getMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS)
    {
        m_State = CMS_FLATMOVE;
        dLastX = LeInput::getMouseX();
        dLastY = LeInput::getMouseY();
    }
    else if(LeInput::getScrollYOffset() != 0)
        m_State = CMS_ZOOM;
}

void LeCameraCADBehaviour::processFlyState()
{
    //Process Keyboard input
    //----------------------
    processKeyboard();

    //Process Mouse movement
    //----------------------
    float xoffset = LeInput::getMouseX() - dLastX;
    float yoffset = dLastY - LeInput::getMouseY(); // reversed since y-coordinates go from bottom to top
    dLastX = LeInput::getMouseX();
    dLastY = LeInput::getMouseY();
    processMouseMovement(xoffset, yoffset, true);

    updateCameraVectors();
    updateCameraFocus();

    //Process Mouse scroll
    //--------------------
    processMouseScroll(LeInput::getScrollYOffset());

//    if(LeInput::getMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_RELEASE)
    if(LeInput::getKey(GLFW_KEY_LEFT_SHIFT) == GLFW_RELEASE)
    {
        m_State = CMS_NONE;
    }
}

void LeCameraCADBehaviour::processFocusState()
{
    if(LeInput::getMouseButton(GLFW_MOUSE_BUTTON_LEFT) == GLFW_PRESS)
    {
        //Process Mouse movement
        //----------------------
        float xoffset = LeInput::getMouseX() - dLastX;
        float yoffset = dLastY - LeInput::getMouseY(); // reversed since y-coordinates go from bottom to top


//        processMouseMovement(xoffset, yoffset, true);
        processMouseMovement(xoffset, yoffset, false);

        updateCameraVectors();
        updateCameraPosition();

    }
    dLastX = LeInput::getMouseX();
    dLastY = LeInput::getMouseY();
    //Process Mouse scroll
    //--------------------
    processMouseScroll(LeInput::getScrollYOffset());


    if(LeInput::getKey(GLFW_KEY_LEFT_ALT) == GLFW_RELEASE)
    {
        m_State = CMS_NONE;
    }
}


void LeCameraCADBehaviour::processFlatMoveState()
{
    float xoffset = LeInput::getMouseX() - dLastX;
    float yoffset = dLastY - LeInput::getMouseY(); // reversed since y-coordinates go from bottom to top


    Focus -= xoffset * flatMoveSensitivity * Right;
    Focus -= yoffset * flatMoveSensitivity * Up;

    updateCameraPosition();


    dLastX = LeInput::getMouseX();
    dLastY = LeInput::getMouseY();

    if(LeInput::getMouseButton(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_RELEASE)
    {
        m_State = CMS_NONE;
    }
}

void LeCameraCADBehaviour::processZoomState()
{
    processMouseScroll(LeInput::getScrollYOffset());
    m_State = CMS_NONE;
}

void LeCameraCADBehaviour::processKeyboard()
{
    float velocity = moveSpeed * LeTime::dDeltaTime;
    if (LeInput::getKey(GLFW_KEY_W) == GLFW_PRESS)
        m_pTransform->m_Position += Front * velocity;
    if (LeInput::getKey(GLFW_KEY_S) == GLFW_PRESS)
        m_pTransform->m_Position -= Front * velocity;
    if (LeInput::getKey(GLFW_KEY_A) == GLFW_PRESS)
        m_pTransform->m_Position -= Right * velocity;
    if (LeInput::getKey(GLFW_KEY_D) == GLFW_PRESS)
        m_pTransform->m_Position += Right * velocity;
}

void LeCameraCADBehaviour::processMouseMovement(float xoffset, float yoffset, bool constrainPitch)
{
    xoffset *= mouseSensitivity;
    yoffset *= mouseSensitivity;

    // TODO: restrain yaw and pitch in a range like (0, 360).
    //  use 180 degree to determain which direction should we rotate to
    yaw   += xoffset;
    pitch += yoffset;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (constrainPitch)
    {
        if (pitch > 89.0f)
            pitch = 89.0f;
        if (pitch < -89.0f)
            pitch = -89.0f;
    }
}

void LeCameraCADBehaviour::processMouseScroll(float yoffset)
{
    zoom -= (float)yoffset;
    if (zoom < 30.0f)
        zoom = 30.0f;
    if (zoom > 45.0f)
        zoom = 45.0f;
}

void LeCameraCADBehaviour::updateCameraVectors()
{
// calculate the new Front vector
    glm::vec3 front;
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    Front = glm::normalize(front);
    // also re-calculate the Right and Up vector
    Right = glm::normalize(glm::cross(Front, WorldUp));  // normalize the vectors, because their length gets closer to 0 the more you look up or down which results in slower movement.
    Up    = glm::normalize(glm::cross(Right, Front));
}

void LeCameraCADBehaviour::updateCameraFocus()
{
    Focus = m_pTransform->m_Position + focusDistance * Front;
}

void LeCameraCADBehaviour::updateCameraPosition()
{
    glm::vec3 newPosition = Focus - focusDistance * Front;
    m_pTransform->setPosition(newPosition.x, newPosition.y, newPosition.z);
}

void LeCameraCADBehaviour::setIsPerspectiveView(bool bIsPerspectiveView)
{
    LeCameraCADBehaviour::bIsPerspectiveView = bIsPerspectiveView;
}

void LeCameraCADBehaviour::flipViewType()
{
    bIsPerspectiveView = !bIsPerspectiveView;
}
